spb/autoscholar
Public
1import { listTopics } from "@autoscholar/core";2import { papers, paperTopics, paperVersions, series } from "@autoscholar/db";3import type { PublicPaperSummary } from "@autoscholar/schemas";4import { and, desc, eq, inArray } from "drizzle-orm";5import type { Metadata } from "next";6import Link from "next/link";7import { notFound } from "next/navigation";8import { Disclosure } from "@/components/Disclosure";9import { EmptyState } from "@/components/EmptyState";10import { PaperCard } from "@/components/PaperCard";11import { db } from "@/lib/server/db";1213export const dynamic = "force-dynamic";1415export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {16 const { slug } = await params;17 const t = (await listTopics(db())).find((x) => x.slug === slug);18 return { title: t ? `${t.name} — topic` : "Topic", alternates: { canonical: `/topics/${slug}` } };19}2021export default async function TopicPage({ params }: { params: Promise<{ slug: string }> }) {22 const { slug } = await params;23 const d = db();24 const topics = await listTopics(d);25 const topic = topics.find((x) => x.slug === slug);26 if (!topic) notFound();27 const children = topics.filter((c) => c.parentId === topic.id);28 const parent = topics.find((c) => c.id === topic.parentId);29 const ids = [topic.id, ...children.map((c) => c.id)];30 const staging = process.env.STAGING_MODE === "1";31 const rows = await d32 .selectDistinctOn([papers.id], { p: papers, v: paperVersions, s: { slug: series.slug } })33 .from(paperTopics)34 .innerJoin(papers, eq(paperTopics.paperId, papers.id))35 .innerJoin(paperVersions, eq(papers.currentVersionId, paperVersions.id))36 .leftJoin(series, eq(papers.seriesId, series.id))37 .where(and(inArray(paperTopics.topicId, ids), staging ? undefined : eq(papers.synthetic, false)))38 .orderBy(papers.id, desc(papers.firstPublishedAt));39 const items: PublicPaperSummary[] = rows40 .map((r) => ({41 publicId: r.p.publicId,42 slug: r.p.slug,43 title: r.v.title,44 abstract: r.v.abstract,45 keywords: r.v.keywords,46 discipline: r.p.discipline,47 researchMode: r.p.researchMode,48 status: r.p.status as PublicPaperSummary["status"],49 versionNo: r.v.versionNo,50 firstPublishedAt: r.p.firstPublishedAt.toISOString(),51 updatedAt: r.p.updatedAt.toISOString(),52 seriesSlug: r.s?.slug ?? null,53 topics: [],54 hasCode: r.v.artifactSet.some((a) => a.kind === "code"),55 hasData: r.v.artifactSet.some((a) => a.kind === "data" && !/README|manifest/.test(a.path)),56 hasLatex: r.v.artifactSet.some((a) => a.path.endsWith(".tex")),57 synthetic: r.p.synthetic,58 }))59 .sort((a, b) => b.firstPublishedAt.localeCompare(a.firstPublishedAt));60 return (61 <div className="container-x section-tight space-y-8">62 <header className="max-w-2xl">63 <p className="eyebrow">64 <Link href="/topics">Topics</Link>65 {parent && (66 <>67 {" "}68 / <Link href={`/topics/${parent.slug}`}>{parent.name}</Link>69 </>70 )}71 </p>72 <h1 className="title-1 mt-2">{topic.name}</h1>73 {topic.description && <p className="muted mt-2 max-w-2xl text-sm">{topic.description}</p>}74 {children.length > 0 && (75 <ul className="mt-3 flex flex-wrap gap-1.5" aria-label="Subfields">76 {children.map((c) => (77 <li key={c.id}>78 <Link79 href={`/topics/${c.slug}`}80 className="chip no-underline"81 style={{ minHeight: "1.75rem" }}82 >83 {c.name}84 </Link>85 </li>86 ))}87 </ul>88 )}89 </header>90 {items.length === 0 ? (91 <EmptyState title={`No working papers in ${topic.name} yet.`}>92 Papers appear here once they pass every quality gate.93 </EmptyState>94 ) : (95 <ul className="border-t hairline">96 {items.map((p, i) => (97 <li key={p.publicId}>98 <PaperCard p={p} variant="row" index={i} />99 </li>100 ))}101 </ul>102 )}103 <Disclosure compact />104 </div>105 );106}107